/**
 * @file        Color.ts
 * @author      shenyc
 * @date        2023-05-06 - ?
 * @copyright   Copy right (c) shenyc (shenyczz@163.com).
 *              All rights reserved.
 *
 ******************************************************************************/
// {{@@@}}
"use strict";

import { isDefined } from "./determines";
import { defaultValue } from "./defaultValue";
// ----------------------------------------------------------------------------
/**
 * A color, specified using red, green, blue, and alpha values,
 * which range from <code>0</code> (no intensity) to <code>1.0</code> (full intensity).
 *
 * @param {number} [red=1.0] The red component.
 * @param {number} [green=1.0] The green component.
 * @param {number} [blue=1.0] The blue component.
 * @param {number} [alpha=1.0] The alpha component.
 *
 * @see Packable
 */
class Color {
    constructor(red?: number, green?: number, blue?: number, alpha?: number) {
        this.red = defaultValue(red, 1.0);
        this.green = defaultValue(green, 1.0);
        this.blue = defaultValue(blue, 1.0);
        this.alpha = defaultValue(alpha, 1.0);
    }

    /**
     * The red component.
     * @type {number}
     * @default 1.0
     */
    public red: number;
    /**
     * The green component.
     * @type {number}
     * @default 1.0
     */
    public green: number;
    /**
     * The blue component.
     * @type {number}
     * @default 1.0
     */
    public blue: number;
    /**
     * The alpha component.
     * @type {number}
     * @default 1.0
     */
    public alpha: number;

    /**
     * Duplicates a Color.
     * @returns {Color} The modified result parameter or a new instance if result was undefined. (Returns undefined if color is undefined)
     */
    public clone(): Color {
        return new Color(this.red, this.green, this.blue, this.alpha);
    }

    /**
     * Converts this color to an array of red, green, blue, and alpha values
     * that are in the range of 0 to 255.
     * @returns {number[]} The modified result parameter or a new instance if result was undefined.
     */
    public toBytes(): number[] {
        const red = Color.floatToByte(this.red);
        const green = Color.floatToByte(this.green);
        const blue = Color.floatToByte(this.blue);
        const alpha = Color.floatToByte(this.alpha);
        return [red, green, blue, alpha];
    }

    /**
     * Creates a string representing this Color in the format '(red, green, blue, alpha)'.
     * @returns {string} A string representing this Color in the format '(red, green, blue, alpha)'.
     */
    public toString(): string {
        return `(${this.red}, ${this.green}, ${this.blue}, ${this.alpha})`;
    }

    /**
     * Creates a string containing the CSS color value for this color.
     * @returns {string} The CSS equivalent of this color.
     * @see {@link http://www.w3.org/TR/css3-color/#rgba-color|CSS RGB or RGBA color values}
     */
    public toCssColorString(): string {
        const red = Color.floatToByte(this.red);
        const green = Color.floatToByte(this.green);
        const blue = Color.floatToByte(this.blue);
        if (this.alpha === 1) {
            return `rgb(${red},${green},${blue})`;
        }
        return `rgba(${red},${green},${blue},${this.alpha})`;
    }

    /**
     * Creates a string containing CSS hex string color value for this color.
     * @returns {string} The CSS hex string equivalent of this color.
     */
    public toCssHexString(): string {
        let r = Color.floatToByte(this.red).toString(16);
        if (r.length < 2) {
            r = `0${r}`;
        }
        let g = Color.floatToByte(this.green).toString(16);
        if (g.length < 2) {
            g = `0${g}`;
        }
        let b = Color.floatToByte(this.blue).toString(16);
        if (b.length < 2) {
            b = `0${b}`;
        }
        if (this.alpha < 1) {
            let hexAlpha = Color.floatToByte(this.alpha).toString(16);
            if (hexAlpha.length < 2) {
                hexAlpha = `0${hexAlpha}`;
            }
            return `#${r}${g}${b}${hexAlpha}`;
        }
        return `#${r}${g}${b}`;
    }
    // ------------------------------------------------------------------------ static functions begin
    /**
     * Creates a new Color specified using red, green, blue, and alpha values
     * that are in the range of 0 to 255, converting them internally to a range of 0.0 to 1.0.
     *
     * @param {number} [red=255] The red component.
     * @param {number} [green=255] The green component.
     * @param {number} [blue=255] The blue component.
     * @param {number} [alpha=255] The alpha component.
     * @returns {Color} The modified result parameter or a new Color instance if one was not provided.
     */
    public static fromBytes(red: number, green: number, blue: number, alpha: number): Color {
        red = Color.byteToFloat(defaultValue(red, 255.0));
        green = Color.byteToFloat(defaultValue(green, 255.0));
        blue = Color.byteToFloat(defaultValue(blue, 255.0));
        alpha = Color.byteToFloat(defaultValue(alpha, 255.0));
        return new Color(red, green, blue, alpha);
    }

    /**
     * Creates a new Color from a single numeric unsigned 32-bit RGBA value, using the endianness
     * of the system.
     *
     * @param {number} rgba A single numeric unsigned 32-bit RGBA value.
     * @returns {Color} The color object.
     *
     * @example
     * const color = Cesium.Color.fromRgba(0x67ADDFFF);
     *
     * @see Color#toRgba
     */
    public static fromRgba(rgba: number): Color {
        // scratchUint32Array and scratchUint8Array share an underlying array buffer
        Color.scratchUint32Array[0] = rgba;
        return Color.fromBytes(Color.scratchUint8Array[0], Color.scratchUint8Array[1], Color.scratchUint8Array[2], Color.scratchUint8Array[3]);
    }

    /**
     * Creates a new Color that has the same red, green, and blue components
     * of the specified color, but with the specified alpha value.
     *
     * @param {Color} baseColor The base color
     * @param {number} alpha The new alpha component.
     * @returns {Color} The modified result parameter or a new Color instance if one was not provided.
     *
     * @example const translucentRed = Cesium.Color.fromAlpha(Cesium.Color.RED, 0.9);
     */
    static fromAlpha(baseColor: Color, alpha: number): Color {
        const result = baseColor.clone();
        result.alpha = alpha;
        return result;
    }
    /**
     * Stores the provided instance into the provided array.
     *
     * @param {Color} value The value to pack.
     * @param {number[]} array The array to pack into.
     * @param {number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {number[]} The array that was packed into
     */
    public static pack(value: Color, array: number[], startingIndex: number = 0): number[] {
        startingIndex = defaultValue(startingIndex, 0);
        array[startingIndex++] = value.red;
        array[startingIndex++] = value.green;
        array[startingIndex++] = value.blue;
        array[startingIndex] = value.alpha;
        return array;
    }

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {number[]} array The packed array.
     * @param {number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {Color} [result] The object into which to store the result.
     * @returns {Color} The modified result parameter or a new Color instance if one was not provided.
     */
    public static unpack(array: number[], startingIndex: number = 0): Color {
        const red = array[startingIndex++];
        const green = array[startingIndex++];
        const blue = array[startingIndex++];
        const alpha = array[startingIndex];
        return new Color(red, green, blue, alpha);
    }
    /**
     * Returns true if the first Color equals the second color.
     *
     * @param {Color} left The first Color to compare for equality.
     * @param {Color} right The second Color to compare for equality.
     * @returns {boolean} <code>true</code> if the Colors are equal; otherwise, <code>false</code>.
     */
    public static equals(left: Color, right: Color): boolean {
        return (
            left === right ||
            (isDefined(left) &&
                isDefined(right) &&
                left.red === right.red &&
                left.green === right.green &&
                left.blue === right.blue &&
                left.alpha === right.alpha)
        );
    }

    /**
     * Creates a Color instance from hue, saturation, and lightness.
     *
     * @param {number} [hue=0] The hue angle 0...1
     * @param {number} [saturation=0] The saturation value 0...1
     * @param {number} [lightness=0] The lightness value 0...1
     * @param {number} [alpha=1.0] The alpha component 0...1
     * @returns {Color} The color object.
     *
     * @see {@link http://www.w3.org/TR/css3-color/#hsl-color|CSS color values}
     */
    public static fromHsl(hue: number, saturation: number, lightness: number, alpha: number): Color {
        hue = defaultValue(hue, 0.0) % 1.0;
        saturation = defaultValue(saturation, 0.0);
        lightness = defaultValue(lightness, 0.0);
        alpha = defaultValue(alpha, 1.0);

        let red = lightness;
        let green = lightness;
        let blue = lightness;

        if (saturation !== 0) {
            let m2 = 0;
            if (lightness < 0.5) {
                m2 = lightness * (1 + saturation);
            } else {
                m2 = lightness + saturation - lightness * saturation;
            }

            const m1 = 2.0 * lightness - m2;
            red = Color.hue2rgb(m1, m2, hue + 1 / 3);
            green = Color.hue2rgb(m1, m2, hue);
            blue = Color.hue2rgb(m1, m2, hue - 1 / 3);
        }

        return new Color(red, green, blue, alpha);
    }

    /**
     * Creates a Color instance from a CSS color value.
     *
     * @param {string} color The CSS color value in #rgb, #rgba, #rrggbb, #rrggbbaa, rgb(), rgba(), hsl(), or hsla() format.
     * @returns {Color} The color object, or undefined if the string was not a valid CSS color.
     *
     * @example
     * const red = Color.fromCssColorString('red');
     * const blue = Color.fromCssColorString('#67ADDF');
     *
     * @see {@link http://www.w3.org/TR/css3-color|CSS color values}
     */
    public static fromCssColorString(color: string, defaultColor: Color = new Color()): Color {
        // Remove all surrounding whitespaces from the color string
        color = color.trim();

        let matches = Color.rgbaMatcher.exec(color);
        if (matches !== null) {
            const red = parseInt(matches[1], 16) / 15;
            const green = parseInt(matches[2], 16) / 15.0;
            const blue = parseInt(matches[3], 16) / 15.0;
            const alpha = parseInt(defaultValue(matches[4], "f"), 16) / 15.0;
            return new Color(red, green, blue, alpha);
        }

        matches = Color.rrggbbaaMatcher.exec(color);
        if (matches !== null) {
            const red = parseInt(matches[1], 16) / 255.0;
            const green = parseInt(matches[2], 16) / 255.0;
            const blue = parseInt(matches[3], 16) / 255.0;
            const alpha = parseInt(defaultValue(matches[4], "ff"), 16) / 255.0;
            return new Color(red, green, blue, alpha);
        }

        matches = Color.rgbParenthesesMatcher.exec(color);
        if (matches !== null) {
            const red = parseFloat(matches[1]) / ("%" === matches[1].substr(-1) ? 100.0 : 255.0);
            const green = parseFloat(matches[2]) / ("%" === matches[2].substr(-1) ? 100.0 : 255.0);
            const blue = parseFloat(matches[3]) / ("%" === matches[3].substr(-1) ? 100.0 : 255.0);
            const alpha = parseFloat(defaultValue(matches[4], "1.0"));
            return new Color(red, green, blue, alpha);
        }

        matches = Color.hslParenthesesMatcher.exec(color);
        if (matches !== null) {
            return Color.fromHsl(
                parseFloat(matches[1]) / 360.0,
                parseFloat(matches[2]) / 100.0,
                parseFloat(matches[3]) / 100.0,
                parseFloat(defaultValue(matches[4], "1.0"))
            );
        }

        return defaultColor;
    }

    /**
     * Converts a 'byte' color component in the range of 0 to 255 into
     * a 'float' color component in the range of 0 to 1.0.
     *
     * @param {number} byte The number to be converted.
     * @returns {number} The converted number.
     */
    public static byteToFloat(byte: number): number {
        return byte / 255.0;
    }
    /**
     * Converts a 'float' color component in the range of 0 to 1.0 into
     * a 'byte' color component in the range of 0 to 255.
     *
     * @param {number} val The number to be converted.
     * @returns {number} The converted number.
     */
    public static floatToByte(val: number): number {
        return val === 1.0 ? 255.0 : (val * 256.0) | 0;
    }

    private static hue2rgb(m1: number, m2: number, h: number): number {
        if (h < 0) {
            h += 1;
        }
        if (h > 1) {
            h -= 1;
        }
        if (h * 6 < 1) {
            return m1 + (m2 - m1) * 6 * h;
        }
        if (h * 2 < 1) {
            return m2;
        }
        if (h * 3 < 2) {
            return m1 + (m2 - m1) * (2 / 3 - h) * 6;
        }
        return m1;
    }

    // ------------------------------------------------------------------------ static functions begin

    // --------------
}

namespace Color {
    // #rgba
    export const rgbaMatcher: RegExp = /^#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])?$/i;
    // #rrggbbaa
    export const rrggbbaaMatcher: RegExp = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/i;
    // rgb(), rgba(), or rgb%()
    export const rgbParenthesesMatcher: RegExp = /^rgba?\s*\(\s*([0-9.]+%?)\s*[,\s]+\s*([0-9.]+%?)\s*[,\s]+\s*([0-9.]+%?)(?:\s*[,\s/]+\s*([0-9.]+))?\s*\)$/i;
    // hsl() or hsla()
    export const hslParenthesesMatcher: RegExp = /^hsla?\s*\(\s*([0-9.]+)\s*[,\s]+\s*([0-9.]+%)\s*[,\s]+\s*([0-9.]+%)(?:\s*[,\s/]+\s*([0-9.]+))?\s*\)$/i;

    // import FeatureDetection from "./FeatureDetection.js";
    const scratchArrayBuffer = new ArrayBuffer(4);
    export const scratchUint8Array = new Uint8Array(scratchArrayBuffer);
    export const scratchUint32Array = new Uint32Array(scratchArrayBuffer);

    export const WHITE: Color = Object.freeze(new Color()) as Color;

    /**
     * An immutable Color instance initialized to CSS color #F0F8FF
     * <span class="colorSwath" style="background: #F0F8FF;"></span>
     *
     * @constant
     * @type {Color}
     */
    export const ALICEBLUE: Color = Object.freeze(Color.fromCssColorString("#F0F8FF"));

    /**
     * An immutable Color instance initialized to CSS color #FAEBD7
     * <span class="colorSwath" style="background: #FAEBD7;"></span>
     *
     * @constant
     * @type {Color}
     */
    export const ANTIQUEWHITE: Color = Object.freeze(Color.fromCssColorString("#FAEBD7"));

    /**
     * An immutable Color instance initialized to CSS color #00FFFF
     * <span class="colorSwath" style="background: #00FFFF;"></span>
     *
     * @constant
     * @type {Color}
     */
    export const AQUA: Color = Object.freeze(Color.fromCssColorString("#00FFFF"));

/**
 * An immutable Color instance initialized to CSS color #7FFFD4
 * <span class="colorSwath" style="background: #7FFFD4;"></span>
 *
 * @constant
 * @type {Color}
 */
export const AQUAMARINE: Color = Object.freeze(Color.fromCssColorString("#7FFFD4"));

/**
 * An immutable Color instance initialized to CSS color #F0FFFF
 * <span class="colorSwath" style="background: #F0FFFF;"></span>
 *
 * @constant
 * @type {Color}
 */
export const AZURE: Color = Object.freeze(Color.fromCssColorString("#F0FFFF"));

/**
 * An immutable Color instance initialized to CSS color #F5F5DC
 * <span class="colorSwath" style="background: #F5F5DC;"></span>
 *
 * @constant
 * @type {Color}
 */
export const BEIGE: Color = Object.freeze(Color.fromCssColorString("#F5F5DC"));

/**
 * An immutable Color instance initialized to CSS color #FFE4C4
 * <span class="colorSwath" style="background: #FFE4C4;"></span>
 *
 * @constant
 * @type {Color}
 */
export const BISQUE: Color = Object.freeze(Color.fromCssColorString("#FFE4C4"));

/**
 * An immutable Color instance initialized to CSS color #000000
 * <span class="colorSwath" style="background: #000000;"></span>
 *
 * @constant
 * @type {Color}
 */
export const BLACK: Color = Object.freeze(Color.fromCssColorString("#000000"));

/**
 * An immutable Color instance initialized to CSS color #FFEBCD
 * <span class="colorSwath" style="background: #FFEBCD;"></span>
 *
 * @constant
 * @type {Color}
 */
export const BLANCHEDALMOND: Color = Object.freeze(Color.fromCssColorString("#FFEBCD"));

/**
 * An immutable Color instance initialized to CSS color #0000FF
 * <span class="colorSwath" style="background: #0000FF;"></span>
 *
 * @constant
 * @type {Color}
 */
export const BLUE: Color = Object.freeze(Color.fromCssColorString("#0000FF"));

/**
 * An immutable Color instance initialized to CSS color #8A2BE2
 * <span class="colorSwath" style="background: #8A2BE2;"></span>
 *
 * @constant
 * @type {Color}
 */
export const BLUEVIOLET: Color = Object.freeze(Color.fromCssColorString("#8A2BE2"));

/**
 * An immutable Color instance initialized to CSS color #A52A2A
 * <span class="colorSwath" style="background: #A52A2A;"></span>
 *
 * @constant
 * @type {Color}
 */
export const BROWN: Color = Object.freeze(Color.fromCssColorString("#A52A2A"));

/**
 * An immutable Color instance initialized to CSS color #DEB887
 * <span class="colorSwath" style="background: #DEB887;"></span>
 *
 * @constant
 * @type {Color}
 */
export const BURLYWOOD: Color = Object.freeze(Color.fromCssColorString("#DEB887"));

/**
 * An immutable Color instance initialized to CSS color #5F9EA0
 * <span class="colorSwath" style="background: #5F9EA0;"></span>
 *
 * @constant
 * @type {Color}
 */
export const CADETBLUE: Color = Object.freeze(Color.fromCssColorString("#5F9EA0"));
/**
 * An immutable Color instance initialized to CSS color #7FFF00
 * <span class="colorSwath" style="background: #7FFF00;"></span>
 *
 * @constant
 * @type {Color}
 */
export const CHARTREUSE: Color = Object.freeze(Color.fromCssColorString("#7FFF00"));

/**
 * An immutable Color instance initialized to CSS color #D2691E
 * <span class="colorSwath" style="background: #D2691E;"></span>
 *
 * @constant
 * @type {Color}
 */
export const CHOCOLATE: Color = Object.freeze(Color.fromCssColorString("#D2691E"));

/**
 * An immutable Color instance initialized to CSS color #FF7F50
 * <span class="colorSwath" style="background: #FF7F50;"></span>
 *
 * @constant
 * @type {Color}
 */
export const CORAL: Color = Object.freeze(Color.fromCssColorString("#FF7F50"));

/**
 * An immutable Color instance initialized to CSS color #6495ED
 * <span class="colorSwath" style="background: #6495ED;"></span>
 *
 * @constant
 * @type {Color}
 */
export const CORNFLOWERBLUE: Color = Object.freeze(Color.fromCssColorString("#6495ED"));

/**
 * An immutable Color instance initialized to CSS color #FFF8DC
 * <span class="colorSwath" style="background: #FFF8DC;"></span>
 *
 * @constant
 * @type {Color}
 */
export const CORNSILK: Color = Object.freeze(Color.fromCssColorString("#FFF8DC"));

/**
 * An immutable Color instance initialized to CSS color #DC143C
 * <span class="colorSwath" style="background: #DC143C;"></span>
 *
 * @constant
 * @type {Color}
 */
export const CRIMSON: Color = Object.freeze(Color.fromCssColorString("#DC143C"));

/**
 * An immutable Color instance initialized to CSS color #00FFFF
 * <span class="colorSwath" style="background: #00FFFF;"></span>
 *
 * @constant
 * @type {Color}
 */
export const CYAN: Color = Object.freeze(Color.fromCssColorString("#00FFFF"));

/**
 * An immutable Color instance initialized to CSS color #00008B
 * <span class="colorSwath" style="background: #00008B;"></span>
 *
 * @constant
 * @type {Color}
 */
export const DARKBLUE: Color = Object.freeze(Color.fromCssColorString("#00008B"));

/**
 * An immutable Color instance initialized to CSS color #008B8B
 * <span class="colorSwath" style="background: #008B8B;"></span>
 *
 * @constant
 * @type {Color}
 */
export const DARKCYAN: Color = Object.freeze(Color.fromCssColorString("#008B8B"));

/**
 * An immutable Color instance initialized to CSS color #B8860B
 * <span class="colorSwath" style="background: #B8860B;"></span>
 *
 * @constant
 * @type {Color}
 */
export const DARKGOLDENROD: Color = Object.freeze(Color.fromCssColorString("#B8860B"));

/**
 * An immutable Color instance initialized to CSS color #A9A9A9
 * <span class="colorSwath" style="background: #A9A9A9;"></span>
 *
 * @constant
 * @type {Color}
 */
export const DARKGRAY: Color = Object.freeze(Color.fromCssColorString("#A9A9A9"));

/**
 * An immutable Color instance initialized to CSS color #006400
 * <span class="colorSwath" style="background: #006400;"></span>
 *
 * @constant
 * @type {Color}
 */
export const DARKGREEN: Color = Object.freeze(Color.fromCssColorString("#006400"));

/**
 * An immutable Color instance initialized to CSS color #A9A9A9
 * <span class="colorSwath" style="background: #A9A9A9;"></span>
 *
 * @constant
 * @type {Color}
 */
export const DARKGREY: Color = Color.DARKGRAY;

/**
 * An immutable Color instance initialized to CSS color #BDB76B
 * <span class="colorSwath" style="background: #BDB76B;"></span>
 *
 * @constant
 * @type {Color}
 */
export const DARKKHAKI: Color = Object.freeze(Color.fromCssColorString("#BDB76B"));

/**
 * An immutable Color instance initialized to CSS color #8B008B
 * <span class="colorSwath" style="background: #8B008B;"></span>
 *
 * @constant
 * @type {Color}
 */
export const DARKMAGENTA: Color = Object.freeze(Color.fromCssColorString("#8B008B"));


}

export default Color;
// ----------------------------------------------------------------------------
// {{@@@}}
